#include <map>
#include <unordered_map>
#include <memory>
#include <thread>
#include <iostream>
#include "Poco/BasicEvent.h"
#include "Poco/Delegate.h"
#include "Poco/Exception.h"
#include "BaseEvent.h"

using namespace SystemTool;

template <class TArgs, class TDelegate = Poco::AbstractDelegate<TArgs>>
class MyNotificationStrategy
{
public:
    // Sends a notification to all registered delegates.
    virtual void Notify(const void *pSender, int iEventId, TArgs &Args)
    {
        if(m_EventMap.find(iEventId) == m_EventMap.end())
        {
            return;
        }
        std::vector<TDelegate*> &vTable = m_EventMap[iEventId];
        for (size_t index = 0; index < vTable.size(); index++)
        {
            TDelegate* pDelegate = vTable.at(index);
            pDelegate->notify(pSender, Args);
        }
    }
		
    // Adds a delegate to the strategy.
    virtual TDelegate* Add(int iEventId, TDelegate* pDeleGateFunc)
    {
        std::vector<TDelegate*> &vTable = m_EventMap[iEventId];

        if (pDeleGateFunc == nullptr)
        {
            return nullptr;
        }

        if (vTable.empty())
        {
            vTable.push_back(pDeleGateFunc);
            return nullptr;
        }

        auto Iter = std::find_if(vTable.begin(), vTable.end(), [&pDeleGateFunc](TDelegate* pItem){
            if(pItem == pDeleGateFunc)
            {
                return true;
            }
            return false; 
        });

        if (Iter != vTable.end())
        {
            vTable.erase(Iter);
        }
        vTable.push_back(pDeleGateFunc);

        m_EventMap[iEventId] = vTable;

        return pDeleGateFunc;
    }

    // Removes a delegate from the strategy, if found.
    virtual void Remove(int iEventId, TDelegate* pDeleGateFunc)
    {
        std::vector<TDelegate*> &vTable = m_EventMap[iEventId];

        if (vTable.empty())
        {
            return;
        }

        if(pDeleGateFunc == nullptr)
        {
            return;
        }

        auto Iter = std::find_if(vTable.begin(), vTable.end(), [&pDeleGateFunc](TDelegate* pItem){
            Poco::AbstractDelegate<EventArgs>* pA = (Poco::AbstractDelegate<EventArgs>*)pItem;
            Poco::AbstractDelegate<EventArgs>* pB = (Poco::AbstractDelegate<EventArgs>*)pDeleGateFunc;

            if (pA->equals(*pB))
            {
                return true;
            }
            return false; 
        });

        if (Iter != vTable.end())
        {
            vTable.erase(Iter);
        }

        m_EventMap[iEventId] = vTable;
    }

    // Removes all delegates from the strategy.
	virtual void Clear()
    {
        for (typename std::unordered_map<int, std::vector<TDelegate *>>::iterator iter = m_EventMap.begin(); iter != m_EventMap.end(); iter++)
        {
            std::vector<TDelegate *>& vTable = iter->second;
            for (size_t index = 0; index < vTable.size(); index++)
            {
                TDelegate *pFunc = vTable[index];
                if (pFunc)
                {
                    delete pFunc;
                }
            }
            vTable.clear();
        }
        
        m_EventMap.clear();
    }

    // Returns false if the strategy contains at least one delegate.
	virtual bool Empty() const
    {
        return m_EventMap.empty();
    }

private:
    std::unordered_map<int, std::vector<TDelegate*> > m_EventMap;
};

template <class TArgs, class TStrategy, class TDelegate, class TMutex = Poco::FastMutex>
class MyAbstractEvent
{
public:
    MyAbstractEvent() : _executeAsync(this, &MyAbstractEvent::executeAsyncImpl) {}
    virtual ~MyAbstractEvent() {}

private:
    MyAbstractEvent(const MyAbstractEvent &other) = delete;
    MyAbstractEvent &operator=(const MyAbstractEvent &other) = delete;

public:
    void Register(int iEventId, TDelegate *pDelegate)
    {
		typename TMutex::ScopedLock lock(m_Lock);
        m_Strategy.Add(iEventId, pDelegate);
    }

    void UnRegister(int iEventId, TDelegate *pDelegate)
    {
		typename TMutex::ScopedLock lock(m_Lock);
        m_Strategy.Remove(iEventId, pDelegate);
    }

    void Clear()
	{
		typename TMutex::ScopedLock lock(m_Lock);
		m_Strategy.Clear();
	}

	bool Empty() const
	{
		typename TMutex::ScopedLock lock(m_Lock);
		return m_Strategy.Empty();
	}

    void Notify(const void* pSender, int iEventId, TArgs& Args)
	{
		Poco::ScopedLockWithUnlock<TMutex> lock(m_Lock);
		TStrategy strategy(m_Strategy);
		lock.unlock();
        strategy.Notify(pSender, iEventId, Args);
    }

    Poco::ActiveResult<TArgs> notifyAsync(const void *pSender, int iEventId, const TArgs &Args)
    {
        MyNotifyAsyncParams params(pSender, iEventId, Args);
        {
			typename TMutex::ScopedLock lock(m_Lock);
			params.m_ptrStrat = std::shared_ptr<TStrategy>(new TStrategy(m_Strategy));
		}
		Poco::ActiveResult<TArgs> result = _executeAsync(params);
		return result;
	}

protected:
    struct MyNotifyAsyncParams
	{
        std::shared_ptr<TStrategy> m_ptrStrat;
        const void *m_pSender;
        int m_iEventId;
        TArgs m_Args;

        MyNotifyAsyncParams(const void *pSender, int iEventId, const TArgs &a) : m_ptrStrat(),
                                                                                 m_pSender(pSender),
                                                                                 m_iEventId(iEventId),
                                                                                 m_Args(a) {}
    };

	Poco::ActiveMethod<TArgs, MyNotifyAsyncParams, MyAbstractEvent> _executeAsync;

	TArgs executeAsyncImpl(const MyNotifyAsyncParams& par)
	{
		MyNotifyAsyncParams params = par;
		TArgs retArgs(params.m_Args);
        params.m_ptrStrat->Notify(params.m_pSender, params.m_iEventId, retArgs);
        return retArgs;
	}

private:
    mutable TMutex m_Lock;
    TStrategy m_Strategy;
};

template <class TArgs, class TMutex = Poco::FastMutex>
class MyBaseEvent : public MyAbstractEvent<TArgs, MyNotificationStrategy<TArgs, Poco::AbstractDelegate<TArgs>>, Poco::AbstractDelegate<TArgs>, TMutex>
{
public:
    MyBaseEvent(){    }
    ~MyBaseEvent(){   }
private:
    MyBaseEvent(const MyBaseEvent &other) = delete;
    MyBaseEvent &operator=(const MyBaseEvent &other) = delete;
};

class MyTarget
{
public:
    // Construct the target
    MyTarget(int iEventId, std::function<void(EventArgs &)> &fFunc)
    {
        m_iEventId= iEventId;
        m_fFunc = fFunc;
    }

    // Handle event
    void OnEvent(EventArgs &Args)
    {
        if(m_fFunc)
        {
            m_fFunc(Args);
        }
    }

    // Event id
    int m_iEventId;

    // Handler
    std::function<void(EventArgs &)> m_fFunc;
};

// Construct the obj
BaseEvent::BaseEvent() : m_pEvent(nullptr), m_pLock(nullptr), m_pTargetTable(nullptr)
{
   m_pEvent = new MyBaseEvent<EventArgs>;
   m_pLock = new std::mutex;
   m_pTargetTable = new std::vector<void*>;
}

// Destruct the obj
BaseEvent::~BaseEvent()
{
    // Clear the table
    Clear();

    MyBaseEvent<EventArgs> *pEvent = (MyBaseEvent<EventArgs> *)m_pEvent;
    if (pEvent)
    {
        pEvent->Clear();
        delete pEvent;
        m_pEvent = nullptr;
    }

    if (m_pLock)
    {
        delete m_pLock;
        m_pLock = nullptr;
    }
}

// Add to table
void BaseEvent::Add(void *pData)
{
    std::lock_guard<std::mutex> Locker(*m_pLock);
    if(m_pTargetTable == nullptr)
    {
        return;
    }

    if(m_pTargetTable->empty())
    {
        m_pTargetTable->push_back(pData);
        return;
    }

    auto Iter = std::find_if(m_pTargetTable->begin(), m_pTargetTable->end(), [&pData](void* pItem) {
        MyTarget* pTarget = (MyTarget*)pItem;
        MyTarget* pTarget2 = (MyTarget*)pData;
        if(pTarget->m_iEventId == pTarget2->m_iEventId
        && pTarget->m_fFunc.target<void(EventArgs &)>() == pTarget2->m_fFunc.target<void(EventArgs &)>())
        {
            return true;
        }
        return false;
    });

    if(Iter != m_pTargetTable->end())
    {
        m_pTargetTable->erase(Iter);
    }
    m_pTargetTable->push_back(pData);
}

// Remove from the table
void BaseEvent::Remove(void *pData)
{
    std::lock_guard<std::mutex> Locker(*m_pLock);
    if(m_pTargetTable == nullptr)
    {
        return;
    }

    if(m_pTargetTable->empty())
    {
        return;
    }

    auto Iter = std::find_if(m_pTargetTable->begin(), m_pTargetTable->end(), [&pData](void* pItem) {
        MyTarget* pTarget = (MyTarget*)pItem;
        MyTarget* pTarget2 = (MyTarget*)pData;
        if(pTarget->m_iEventId == pTarget2->m_iEventId
        && pTarget->m_fFunc.target<void(EventArgs &)>() == pTarget2->m_fFunc.target<void(EventArgs &)>())
        {
            return true;
        }
        return false;
    });

    if(Iter != m_pTargetTable->end())
    {
        m_pTargetTable->erase(Iter);
    }
}

// Clear the table
void BaseEvent::Clear()
{
    std::lock_guard<std::mutex> Locker(*m_pLock);

    if (m_pTargetTable == nullptr)
    {
        return;
    }

    for (size_t index; index < m_pTargetTable->size(); index++)
    {
        MyTarget *pTarget = (MyTarget *)m_pTargetTable->at(index);
        if (pTarget)
        {
            delete pTarget;
        }
    }

    m_pTargetTable->clear();
}

// Register the event
void BaseEvent::Register(int iEventId, std::function<void(EventArgs &)> fEventHandlerFunc)
{
    if(m_pEvent == nullptr)
    {
        return;
    }

    MyBaseEvent<EventArgs> &BE = *((MyBaseEvent<EventArgs> *)m_pEvent);
    MyTarget *pTarget = new MyTarget(iEventId, fEventHandlerFunc);
    Add(pTarget);
    Poco::Delegate<MyTarget, EventArgs, false>* pDeleGate = new Poco::Delegate<MyTarget, EventArgs, false>(pTarget, &MyTarget::OnEvent);
    BE.Register(iEventId, pDeleGate);
}

// Unregister the event
void BaseEvent::UnRegister(int iEventId, std::function<void(EventArgs &)> fEventHandlerFunc)
{
    if(m_pEvent == nullptr)
    {
        return;
    }

    MyBaseEvent<EventArgs> &BE = *((MyBaseEvent<EventArgs> *)m_pEvent);
    MyTarget *pTarget = new MyTarget(iEventId, fEventHandlerFunc);
    Poco::Delegate<MyTarget, EventArgs, false>* pDeleGate = new Poco::Delegate<MyTarget, EventArgs, false>(pTarget, &MyTarget::OnEvent);
    BE.UnRegister(iEventId, pDeleGate);
    Remove(pTarget);
}

// Notify the target
void BaseEvent::Notify(EventArgs &Args)
{
    if(m_pEvent == nullptr)
    {
        return;
    }

    MyBaseEvent<EventArgs> &BE = *((MyBaseEvent<EventArgs> *)m_pEvent);
    BE.Notify(Args.pSender, Args.iEventId, Args);
}

// Notify the target
void BaseEvent::AsynNotify(EventArgs &Args, std::function<void(EventArgs &)> fResFunc, long lTimeout_Ms)
{
    if(m_pEvent == nullptr)
    {
        return;
    }

    MyBaseEvent<EventArgs> &BE = *((MyBaseEvent<EventArgs> *)m_pEvent);
    if(fResFunc == nullptr)
    {
        BE.notifyAsync(Args.pSender, Args.iEventId, Args);
        return;
    }

    Poco::ActiveResult<EventArgs> Result = BE.notifyAsync(Args.pSender, Args.iEventId, Args);
    std::thread t= std::thread([&fResFunc,&lTimeout_Ms,&Result](){
        Result.wait(lTimeout_Ms);
        // std::cout << "AsynNotify " << lTimeout_Ms << "ms timeout";
        fResFunc(Result.data());
    });
    t.detach();
}
